package com.yc.cloud.admin.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import java.io.IOException;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yc.cloud.admin.dto.request.AlarmRecordUploadReqDTO;
import com.yc.cloud.admin.dto.request.AlarmRecordUploadFileReqDTO;
import com.yc.cloud.admin.service.ClientApiService;
import com.yc.cloud.admin.dto.request.ClientStrategyQueryParam;
import com.yc.cloud.admin.service.AlarmRecordService;
import com.yc.cloud.admin.service.DetectionPositionService;
import com.yc.cloud.admin.service.HwDeviceService;
import com.yc.cloud.admin.utils.ByteArrayMultipartFile;
import com.yc.cloud.admin.utils.ImageUtils;
import com.yc.cloud.admin.vo.AlarmRecordVo;
import com.yc.cloud.admin.vo.ClientStrategyVo;
import com.yc.cloud.admin.vo.ClientStrategy2Vo;
import com.yc.cloud.admin.config.FaceRecognitionProperties;
import com.yc.cloud.admin.dto.response.FaceSearchRespDTO;
import com.yc.cloud.admin.dto.response.FaceSearchRespDTO.SearchData;
import com.yc.cloud.admin.dto.response.FaceSearchRespDTO.SearchResult;
import com.yc.cloud.admin.service.FaceRecognitionService;
import com.yc.cloud.entity.UmsAdmin;
import com.yc.cloud.mapper.UmsAdminMapper;
import com.yc.cloud.api.enums.PublishStatusEnum;
import com.yc.cloud.api.enums.HWDeviceStatus;
import com.yc.cloud.common.basic.exception.Asserts;
import com.yc.cloud.api.enums.AlgorithmDetectionTypeEnum;
import com.yc.cloud.common.oss.request.OssUploadBase64Param;
import com.yc.cloud.common.oss.service.OssFileService;
import com.yc.cloud.entity.AlarmRecord;
import com.yc.cloud.entity.AlgAlgorithm;
import com.yc.cloud.entity.AlgModelVersion;
import com.yc.cloud.entity.AlgStrategyRule;
import com.yc.cloud.entity.HwDevice;
import com.yc.cloud.entity.SysApplicationVersion;
import com.yc.cloud.entity.SysApplication;
import com.yc.cloud.mapper.AlgAlgorithmMapper;
import com.yc.cloud.mapper.AlgModelMapper;
import com.yc.cloud.mapper.AlgModelVersionMapper;
import com.yc.cloud.mapper.AlgStrategyAlgorithmMapper;
import com.yc.cloud.mapper.AlgStrategyRuleMapper;
import com.yc.cloud.mapper.HwDeviceGroupStrategyMapper;
import com.yc.cloud.mapper.HwDeviceMapper;
import com.yc.cloud.mapper.HwDeviceCameraMapper;
import com.yc.cloud.admin.service.SysApplicationVersionService;
import com.yc.cloud.mapper.SysApplicationMapper;
import com.yc.cloud.common.cache.service.RedisService;
import com.yc.cloud.api.constants.RedisKeyConstants;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import lombok.extern.slf4j.Slf4j;

/**
 * <p>
 * 客户端API 服务实现类
 * </p>
 *
 * @author kinggu
 * @since 2025-07-16
 */
@Service
@Slf4j
public class ClientApiServiceImpl implements ClientApiService {

    @Resource
    private HwDeviceMapper hwDeviceMapper;
    @Resource
    private HwDeviceGroupStrategyMapper hwDeviceGroupStrategyMapper;
    @Resource
    private AlgStrategyAlgorithmMapper algStrategyAlgorithmMapper;
    @Resource
    private AlgAlgorithmMapper algAlgorithmMapper;
    @Resource
    private AlgStrategyRuleMapper algStrategyRuleMapper;
    @Resource
    private AlgModelVersionMapper algModelVersionMapper;
    @Resource
    private AlgModelMapper algModelMapper;
    @Resource
    private OssFileService ossFileService;
    @Resource
    private HwDeviceCameraMapper hwDeviceCameraMapper;

    @Resource
    private SysApplicationVersionService sysApplicationVersionService;

    @Value("${client.app.application-id:}")
    private Long clientApplicationId;

    @Resource
    private SysApplicationMapper sysApplicationMapper;

    @Resource
    private RedisService<Object> redisService;

    @Resource
    private AlarmRecordService alarmRecordService;

    @Resource
    private DetectionPositionService detectionPositionService;

    @Resource
    private HwDeviceService hwDeviceService;

    @Resource
    private FaceRecognitionService faceRecognitionService;

    @Resource
    private UmsAdminMapper umsAdminMapper;

    @Resource
    private FaceRecognitionProperties faceRecognitionProperties;
    @Resource
    private com.yc.cloud.admin.service.AlarmEmailService alarmEmailService;
    @Resource
    private com.yc.cloud.admin.config.AlarmEmailProperties alarmEmailProperties;

    @Value("${device.onlineStatus.ttlSeconds:180}")
    private long deviceOnlineTtlSeconds;

    @Override
    public List<ClientStrategyVo> getStrategyForDevice(String deviceSn, ClientStrategyQueryParam param) {
        // 1. 根据设备SN查找设备信息
        HwDevice device = hwDeviceMapper.selectOne(new QueryWrapper<HwDevice>().eq(HwDevice.DeviceSn, deviceSn));
        if (device == null) {
            log.error("设备SN " + deviceSn + " 未注册");
            Asserts.fail("设备SN " + deviceSn + " 未注册");
        }

        // 2. 立即更新设备在线状态（设备确实在线了，无论后续验证是否成功）
        // 记录设备/摄像头在线心跳（旁路缓存，短期存活）
        String onlineKey = RedisKeyConstants.HW_DEVICE_ONLINE.replace(RedisKeyConstants.DEVICE_SN, deviceSn);
        redisService.set(onlineKey, HWDeviceStatus.ONLINE.getCode(), deviceOnlineTtlSeconds);
        String cameraOnlineKey = RedisKeyConstants.HW_CAMERA_ONLINE.replace(RedisKeyConstants.DEVICE_SN, deviceSn);
        redisService.set(cameraOnlineKey, HWDeviceStatus.ONLINE.getCode(), deviceOnlineTtlSeconds);

        HwDevice updateOnline = new HwDevice();
        updateOnline.setId(device.getId());
        updateOnline.setStatus(HWDeviceStatus.ONLINE.getCode());
        int updateOlineResult = hwDeviceMapper.updateById(updateOnline);
        if (updateOlineResult <= 0) {
            log.error("设备 " + deviceSn + " 在线状态更新失败:{}", updateOlineResult);
        }

        // 3. 查询应用信息
        SysApplication application = sysApplicationMapper
                .selectOne(new QueryWrapper<SysApplication>().eq(SysApplication.APP_ID, param.getAppId()));
        if (application == null) {
            log.error("应用ID " + param.getAppId() + " 未注册");
            Asserts.fail("应用ID " + param.getAppId() + " 未注册");
        }

        Long applicationId = application.getId();

        // 4. 如果客户端提供了版本号，则更新设备的版本号
        if (param.getVersionNumber() != null) {
            HwDevice updateVersion = new HwDevice();
            updateVersion.setId(device.getId());
            updateVersion.setVersionNumber(param.getVersionNumber());
            // 通过版本号和应用ID查询，查询版本名称
            SysApplicationVersion sysApplicationVersion = sysApplicationVersionService
                    .getByVersionNumberAndApplicationId(param.getVersionNumber(), applicationId);
            if (sysApplicationVersion != null) {
                updateVersion.setVersionName(sysApplicationVersion.getVersionName());
            }
            // log.info("设备 {} 上报版本号: {}", deviceSn, param.getVersionNumber());
            int updateVersionResult = hwDeviceMapper.updateById(updateVersion);
            if (updateVersionResult <= 0) {
                log.error("设备 " + deviceSn + " 版本信息更新失败:{}", updateVersionResult);
            }
        }

        // 5. 检查设备分组
        Long groupId = device.getGroupId();
        if (groupId == null) {
            log.error("设备 " + deviceSn + " 未加入任何分组，无策略可应用");
            Asserts.fail("设备 " + deviceSn + " 未加入任何分组，无策略可应用");
        }

        // 6. 根据 groupId 查找关联的所有策略ID
        List<Long> strategyIds = hwDeviceGroupStrategyMapper.selectStrategyIdsByGroupId(groupId);
        if (CollUtil.isEmpty(strategyIds)) {
            log.error("设备 " + deviceSn + " 未加入任何分组，无策略可应用");
            return Collections.emptyList(); // 没有关联策略，直接返回空列表
        }

        List<ClientStrategyVo> resultList = new ArrayList<>();

        // 获取应用最新版本信息
        Integer appVersionCode = null;
        String appDownloadUrl = null;
        String appIdParam = (param != null ? param.getAppId() : null);
        String effectiveAppId = appIdParam != null ? appIdParam : null;
        if (effectiveAppId != null) {

            if (application != null && String.valueOf(PublishStatusEnum.PUBLISHED.getValue())
                    .equals(String.valueOf(application.getPublishStatus()))) {
                SysApplicationVersion latestVersion = sysApplicationVersionService
                        .getLatestVersionByApplicationId(application.getId());
                if (latestVersion != null) {
                    appVersionCode = latestVersion.getVersionNumber();
                    if (latestVersion.getFileId() != null) {
                        appDownloadUrl = ossFileService.getOssFileUrl(String.valueOf(latestVersion.getFileId()));
                    }
                }
            }
        }

        // 3. 遍历每个策略，查找其下所有已发布的规则
        for (Long strategyId : strategyIds) {
            List<AlgStrategyRule> publishedRules = algStrategyRuleMapper.selectList(
                    new QueryWrapper<AlgStrategyRule>()
                            .eq(AlgStrategyRule.StrategyId, strategyId)
                            .eq(AlgStrategyRule.PublishStatus, PublishStatusEnum.PUBLISHED.getValue()));

            // 4. 遍历每个已发布的规则，为其生成VO
            for (AlgStrategyRule rule : publishedRules) {
                Long modelVersionId = rule.getAlgModelVersionId();
                AlgModelVersion modelVersion = algModelVersionMapper.selectById(modelVersionId);
                if (modelVersion == null) {
                    // 如果模型版本不存在，记录日志并跳过此规则
                    log.warn("规则 {} 指定的模型版本 {} 不存在，已跳过", rule.getId(), modelVersionId);
                    continue;
                }

                String modelFileUrl = ossFileService.getOssFileUrl(String.valueOf(modelVersion.getFileId()));
                if (StrUtil.isBlank(modelFileUrl)) {
                    log.warn("模型版本 {} 未关联有效的模型文件，已跳过", modelVersionId);
                    continue;
                }

                ClientStrategyVo vo = new ClientStrategyVo();
                vo.setStrategyId(strategyId);
                vo.setRuleId(rule.getId());
                vo.setParamConfig(rule.getParamConfig());
                vo.setAlgorithmId(rule.getAlgAlgorithmId());
                vo.setModelVersionId(rule.getAlgModelVersionId());
                vo.setModelFileUrl(modelFileUrl);
                vo.setAppVersionCode(appVersionCode);
                vo.setAppDownloadUrl(appDownloadUrl);

                resultList.add(vo);
            }
        }

        return resultList;
    }

    @Override
    public List<ClientStrategy2Vo> getStrategyForDevice2(String deviceSn, ClientStrategyQueryParam param) {
        // 1. 根据设备SN查找设备信息
        HwDevice device = hwDeviceMapper.selectOne(new QueryWrapper<HwDevice>().eq(HwDevice.DeviceSn, deviceSn));
        if (device == null) {
            log.error("设备SN " + deviceSn + " 未注册");
            Asserts.fail("设备SN " + deviceSn + " 未注册");
        }

        // 2. 立即更新设备在线状态（设备确实在线了，无论后续验证是否成功）
        // 记录设备/摄像头在线心跳（旁路缓存，短期存活）
        String onlineKey = RedisKeyConstants.HW_DEVICE_ONLINE.replace(RedisKeyConstants.DEVICE_SN, deviceSn);
        redisService.set(onlineKey, HWDeviceStatus.ONLINE.getCode(), deviceOnlineTtlSeconds);
        String cameraOnlineKey = RedisKeyConstants.HW_CAMERA_ONLINE.replace(RedisKeyConstants.DEVICE_SN, deviceSn);
        redisService.set(cameraOnlineKey, HWDeviceStatus.ONLINE.getCode(), deviceOnlineTtlSeconds);

        HwDevice updateOnline = new HwDevice();
        updateOnline.setId(device.getId());
        updateOnline.setStatus(HWDeviceStatus.ONLINE.getCode());
        int updateOlineResult = hwDeviceMapper.updateById(updateOnline);
        if (updateOlineResult <= 0) {
            log.error("设备 " + deviceSn + " 在线状态更新失败:{}", updateOlineResult);
        }

        // 3. 查询应用信息
        SysApplication application = sysApplicationMapper
                .selectOne(new QueryWrapper<SysApplication>().eq(SysApplication.APP_ID, param.getAppId()));
        if (application == null) {
            log.error("应用ID " + param.getAppId() + " 未注册");
            Asserts.fail("应用ID " + param.getAppId() + " 未注册");
        }

        Long applicationId = application.getId();

        // 4. 如果客户端提供了版本号，则更新设备的版本号
        if (param.getVersionNumber() != null) {
            HwDevice updateVersion = new HwDevice();
            updateVersion.setId(device.getId());
            updateVersion.setVersionNumber(param.getVersionNumber());
            // 通过版本号和应用ID查询，查询版本名称
            SysApplicationVersion sysApplicationVersion = sysApplicationVersionService
                    .getByVersionNumberAndApplicationId(param.getVersionNumber(), applicationId);
            if (sysApplicationVersion != null) {
                updateVersion.setVersionName(sysApplicationVersion.getVersionName());
            }
            // log.info("设备 {} 上报版本号: {}", deviceSn, param.getVersionNumber());
            int updateVersionResult = hwDeviceMapper.updateById(updateVersion);
            if (updateVersionResult <= 0) {
                log.error("设备 " + deviceSn + " 版本信息更新失败:{}", updateVersionResult);
            }
        }

        // 5. 检查设备分组
        Long groupId = device.getGroupId();
        if (groupId == null) {
            log.error("设备 " + deviceSn + " 未加入任何分组，无策略可应用");
            Asserts.fail("设备 " + deviceSn + " 未加入任何分组，无策略可应用");
        }

        // 6. 根据 groupId 查找关联的所有策略ID
        List<Long> strategyIds = hwDeviceGroupStrategyMapper.selectStrategyIdsByGroupId(groupId);
        if (CollUtil.isEmpty(strategyIds)) {
            log.error("设备 " + deviceSn + " 未加入任何分组，无策略可应用");
            return Collections.emptyList(); // 没有关联策略，直接返回空列表
        }

        // 3. 查询当前设备绑定的摄像头列表信息
        List<com.yc.cloud.admin.vo.HwCameraVo> boundCameras = hwDeviceService.getBoundCameras(device.getId());
        //log.info("设备 {} 绑定的摄像头数量: {}", deviceSn, boundCameras != null ? boundCameras.size() : 0);

        List<ClientStrategy2Vo> resultList = new ArrayList<>();

        // 获取应用最新版本信息
        Integer appVersionCode = null;
        String appDownloadUrl = null;
        String appIdParam = (param != null ? param.getAppId() : null);
        String effectiveAppId = appIdParam != null ? appIdParam : null;
        if (effectiveAppId != null) {

            if (application != null && String.valueOf(PublishStatusEnum.PUBLISHED.getValue())
                    .equals(String.valueOf(application.getPublishStatus()))) {
                SysApplicationVersion latestVersion = sysApplicationVersionService
                        .getLatestVersionByApplicationId(application.getId());
                if (latestVersion != null) {
                    appVersionCode = latestVersion.getVersionNumber();
                    if (latestVersion.getFileId() != null) {
                        appDownloadUrl = ossFileService.getOssFileUrl(String.valueOf(latestVersion.getFileId()));
                    }
                }
            }
        }

        // 4. 遍历每个策略，查找其下所有已发布的规则
        for (Long strategyId : strategyIds) {
            List<AlgStrategyRule> publishedRules = algStrategyRuleMapper.selectList(
                    new QueryWrapper<AlgStrategyRule>()
                            .eq(AlgStrategyRule.StrategyId, strategyId)
                            .eq(AlgStrategyRule.PublishStatus, PublishStatusEnum.PUBLISHED.getValue()));

            // 5. 遍历每个已发布的规则，为其生成VO
            for (AlgStrategyRule rule : publishedRules) {
                Long modelVersionId = rule.getAlgModelVersionId();
                AlgModelVersion modelVersion = algModelVersionMapper.selectById(modelVersionId);
                if (modelVersion == null) {
                    // 如果模型版本不存在，记录日志并跳过此规则
                    log.warn("规则 {} 指定的模型版本 {} 不存在，已跳过", rule.getId(), modelVersionId);
                    continue;
                }

                String modelFileUrl = ossFileService.getOssFileUrl(String.valueOf(modelVersion.getFileId()));
                if (StrUtil.isBlank(modelFileUrl)) {
                    log.warn("模型版本 {} 未关联有效的模型文件，已跳过", modelVersionId);
                    continue;
                }

                ClientStrategy2Vo vo = new ClientStrategy2Vo();
                vo.setStrategyId(strategyId);
                vo.setRuleId(rule.getId());
                vo.setParamConfig(rule.getParamConfig());
                vo.setAlgorithmId(rule.getAlgAlgorithmId());
                vo.setModelVersionId(rule.getAlgModelVersionId());
                vo.setModelFileUrl(modelFileUrl);
                vo.setAppVersionCode(appVersionCode);
                vo.setAppDownloadUrl(appDownloadUrl);
                // 设置摄像头列表
                vo.setCameras(boundCameras);

                // 查询算法信息并设置检测类型和算法名称
                AlgAlgorithm algorithm = algAlgorithmMapper.selectById(rule.getAlgAlgorithmId());
                if (algorithm != null) {
                    vo.setDetectionType(algorithm.getDetectionType());
                    vo.setAlgorithmName(algorithm.getName());
                }

                resultList.add(vo);
            }
        }

        return resultList;
    }

    @Override
    public AlarmRecordVo uploadAlarmInfo(AlarmRecordUploadReqDTO uploadReqDTO) {
        try {
            // 1. 解码Base64图片数据
            byte[] imageBytes = decodeBase64Image(uploadReqDTO.getImageBase64());

            // 2. 上传原始图片到OSS
            String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            String originalFileName = "alarm_" + IdUtil.fastSimpleUUID() + ".jpg";

            // 使用OssFileService上传Base64图片
            OssUploadBase64Param originalParam = new OssUploadBase64Param();
            originalParam.setBase64Data(uploadReqDTO.getImageBase64());
            originalParam.setSuffix("jpg");
            originalParam.setBucket("alarm/" + dateDir);
            originalParam.setClassInfo("alarm");
            originalParam.setCreateBy(0L);

            // 上传原始图片并获取fileId
            String originalImageFileId = ossFileService.uploadByBase64(originalParam).getFileId();

            // 通过alarmType获取算法信息
            AlgAlgorithm algorithm = algAlgorithmMapper.selectById(uploadReqDTO.getAlarmType());
            String alarmMessage = "";
            if (algorithm == null) {
                log.error("未找到对应的算法信息, ID: " + uploadReqDTO.getAlarmType());
                Asserts.fail("未找到对应的算法信息, ID: " + uploadReqDTO.getAlarmType());
                return null;
            }
            alarmMessage = algorithm.getAlarmMessage();

            // 检查算法类型并处理检测位置
            List<AlarmRecordUploadReqDTO.DetectionPositionDTO> detectionPositions = uploadReqDTO
                    .getDetectionPositions();

            // 如果是场景检测类型（未检测到目标才报警），且没有检测位置，则生成全图检测框
            if (AlgorithmDetectionTypeEnum.SCENE_DETECTION.getValue().equals(algorithm.getDetectionType())) {
                detectionPositions = generateFullImageDetectionPosition(imageBytes, algorithm.getName());
            }

            // 3. 人脸识别 - 识别违规人员（在绘制检测框之前执行）
            String violatorName = performFaceRecognitionAndGetName(algorithm, imageBytes);

            // 4. 生成带有检测框的图片并上传到OSS
            String markedImageFileId = null;
            if (detectionPositions != null && !detectionPositions.isEmpty()) {
                try {
                    // 在图片上绘制检测框（如果识别到违规人员，将姓名也绘制在图片上）
                    // 场景检测使用居中显示，目标检测使用默认位置
                    boolean isCenter = AlgorithmDetectionTypeEnum.SCENE_DETECTION.getValue()
                            .equals(algorithm.getDetectionType());
                    byte[] markedImageBytes = ImageUtils.drawDetectionBoxes(imageBytes, detectionPositions,
                            alarmMessage, isCenter, violatorName);

                    // 转换为Base64
                    String markedImageBase64 = "data:image/jpeg;base64," + Base64.encode(markedImageBytes);

                    // 上传带标记的图片
                    String markedFileName = "alarm_" + IdUtil.fastSimpleUUID() + "_marked.jpg";
                    OssUploadBase64Param markedParam = new OssUploadBase64Param();
                    markedParam.setBase64Data(markedImageBase64);
                    markedParam.setSuffix("jpg");
                    markedParam.setBucket("alarm/" + dateDir);
                    markedParam.setClassInfo("alarm_marked");
                    markedParam.setCreateBy(0L);

                    markedImageFileId = ossFileService.uploadByBase64(markedParam).getFileId();
                } catch (IOException e) {
                    log.error("绘制检测框失败", e);
                    // 如果绘制失败，使用原始图片
                    markedImageFileId = originalImageFileId;
                }
            } else {
                // 如果没有检测框信息，使用原始图片
                markedImageFileId = originalImageFileId;
            }
            String alarmName = algorithm.getName();

            // 5. 创建告警记录
            AlarmRecord alarmRecord = new AlarmRecord();
            alarmRecord.setAlarmUid(IdUtil.fastSimpleUUID());
            alarmRecord.setAlgorithmId(Long.valueOf(uploadReqDTO.getAlarmType()));
            alarmRecord.setAlarmName(alarmName);

            // 通过deviceSN获取设备信息,然后设置对应的内容
            HwDevice hwDevice = hwDeviceService.getByDeviceSn(uploadReqDTO.getDeviceSn());
            if (hwDevice == null) {
                Asserts.fail("设备未授权");
                return null;
            }
            alarmRecord.setDeviceId(hwDevice.getId());
            alarmRecord.setDeviceLocation(hwDevice.getLocation());
            
            // 设置摄像头ID：优先使用上传的cameraId，否则查询设备绑定的摄像头
            if (uploadReqDTO.getCameraId() != null) {
                // 如果前端上传了cameraId，直接使用
                alarmRecord.setCameraId(uploadReqDTO.getCameraId());
                log.info("使用上传的摄像头ID: {}, 设备: {}", uploadReqDTO.getCameraId(), hwDevice.getDeviceSn());
            } else {
                // 如果没有上传cameraId，则查询设备绑定的摄像头
                List<Long> cameraIds = hwDeviceCameraMapper.selectCameraIdsByDeviceId(hwDevice.getId());
                if (cameraIds != null && !cameraIds.isEmpty()) {
                    // 如果设备绑定了摄像头，取第一个摄像头ID（通常一个设备绑定一个摄像头）
                    alarmRecord.setCameraId(cameraIds.get(0));
                    log.info("设备 {} 自动查询到绑定的摄像头ID: {}", hwDevice.getDeviceSn(), cameraIds.get(0));
                } else {
                    log.warn("设备 {} 未上传cameraId且未绑定摄像头", hwDevice.getDeviceSn());
                }
            }
            
            alarmRecord.setAlarmTime(LocalDateTime.now());
            alarmRecord.setAlarmImageFileId(originalImageFileId);
            alarmRecord.setAlarmMarkedImageFileId(markedImageFileId);
            alarmRecord.setStatus((byte) 0); // 0-待处理
            alarmRecord.setDeleted(false);

            // 6. 保存人脸识别结果到告警记录（如果之前识别到了人员）
            if (StrUtil.isNotBlank(violatorName)) {
                saveFaceRecognitionResult(alarmRecord, violatorName);
            }

            // 7. 保存告警记录
            boolean saveResult = alarmRecordService.save(alarmRecord);
            if (!saveResult) {
                Asserts.fail("保存告警记录失败");
            }

            // 8. 发送告警邮件（如果需要）
            if (alarmEmailService.shouldSendEmail(alarmRecord.getAlgorithmId())) {
                if (Boolean.TRUE.equals(alarmEmailProperties.getSendAsync())) {
                    alarmEmailService.sendAlarmEmailAsync(alarmRecord);
                } else {
                    alarmEmailService.sendAlarmEmail(alarmRecord);
                }
            }

            // 9. 保存目标检测位置信息
            if (detectionPositions != null && !detectionPositions.isEmpty()) {
                detectionPositionService.batchSave(alarmRecord.getId(), detectionPositions);
            }

            // 9. 返回告警记录VO
            return alarmRecordService.getVOById(alarmRecord.getId());
        } catch (Exception e) {
            log.error("上传报警信息失败", e);
            Asserts.fail("上传报警信息失败: " + e.getMessage());
            return null;
        }
    }

    /**
     * 为场景检测生成全图检测位置
     * 
     * @param imageBytes    图片字节数组
     * @param algorithmName 算法名称
     * @return 检测位置列表
     */
    private List<AlarmRecordUploadReqDTO.DetectionPositionDTO> generateFullImageDetectionPosition(byte[] imageBytes,
            String algorithmName) {
        try {
            BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();

            // 预留边界（图片尺寸的5%）
            int borderMargin = Math.max(imageWidth, imageHeight) / 20;

            AlarmRecordUploadReqDTO.DetectionPositionDTO position = new AlarmRecordUploadReqDTO.DetectionPositionDTO();
            position.setX(borderMargin);
            position.setY(borderMargin);
            position.setWidth(imageWidth - 2 * borderMargin);
            position.setHeight(imageHeight - 2 * borderMargin);
            position.setTargetType(algorithmName);
            position.setConfidence(1.0f); // 场景检测设置为100%置信度

            List<AlarmRecordUploadReqDTO.DetectionPositionDTO> positions = new ArrayList<>();
            positions.add(position);
            return positions;
        } catch (IOException e) {
            log.error("生成全图检测位置失败", e);
            return new ArrayList<>();
        }
    }

    /**
     * 解码Base64图片数据
     * 
     * @param base64Data Base64图片数据
     * @return 图片字节数组
     */
    private byte[] decodeBase64Image(String base64Data) {
        try {
            // 移除Base64前缀（如果存在）
            String cleanBase64 = base64Data;
            if (base64Data.contains(",")) {
                cleanBase64 = base64Data.split(",")[1];
            }
            return Base64.decode(cleanBase64);
        } catch (Exception e) {
            log.error("解码Base64图片数据失败", e);
            throw new RuntimeException("图片数据格式错误");
        }
    }

    /**
     * 执行人脸识别，识别违规人员并返回姓名
     * 根据算法的enable_face_recognition字段判断是否需要进行人脸识别
     *
     * @param algorithm 算法信息
     * @param imageBytes 图片字节数组
     * @return 识别到的违规人员姓名，未识别到返回null
     */
    private String performFaceRecognitionAndGetName(AlgAlgorithm algorithm, byte[] imageBytes) {
        // 卫语句：检查算法是否启用人脸识别
        if (!Boolean.TRUE.equals(algorithm.getEnableFaceRecognition())) {
            log.debug("算法ID {} ({}) 未启用人脸识别，跳过", algorithm.getId(), algorithm.getName());
            return null;
        }

        try {
            log.info("开始人脸识别: 告警类型={}, 算法ID={}", algorithm.getName(), algorithm.getId());

            // 将字节数组转换为 MultipartFile
           MultipartFile imageFile = 
                new ByteArrayMultipartFile(
                    imageBytes,
                    "file",
                    "alarm_image.jpg",
                    "image/jpeg"
                );

            // 调用人脸识别服务进行人脸搜索（使用配置的阈值和topK）
            Float threshold = faceRecognitionProperties.getThreshold();
            Integer topK = faceRecognitionProperties.getTopK();
            FaceSearchRespDTO searchResult = faceRecognitionService.searchFaceByFile(imageFile, threshold, topK);

            // 卫语句：检查识别结果是否有效
            if (searchResult == null || !searchResult.isSuccess() || searchResult.getData() == null) {
                log.warn("人脸识别失败或无结果, message={}", 
                    searchResult != null ? searchResult.getMsg() : "未知错误");
                return null;
            }

            SearchData searchData = searchResult.getData();

            // 卫语句：检查是否有匹配结果
            if (searchData.getResults() == null || searchData.getResults().isEmpty()) {
                log.info("人脸识别未找到匹配: 算法={}", algorithm.getName());
                return null;
            }

            // 获取第一个匹配结果（相似度最高的）
            SearchResult topMatch = searchData.getResults().get(0);
            String faceId = topMatch.getFaceId();

            // 卫语句：检查faceId是否有效
            if (StrUtil.isBlank(faceId)) {
                log.warn("人脸识别返回的faceId为空");
                return null;
            }

            // 通过 faceId 查询用户信息
            UmsAdmin user = umsAdminMapper.selectByFaceId(faceId);

            // 卫语句：检查用户是否存在
            if (user == null) {
                log.warn("人脸识别成功但未找到对应用户: faceId={}", faceId);
                return null;
            }

            // 成功：返回用户姓名
            String userName = StrUtil.isNotBlank(user.getTrueName()) ? user.getTrueName() : user.getUsername();
            log.info("人脸识别成功: 违规人员={}, 用户ID={}, 相似度={}", 
                userName, user.getId(), topMatch.getSimilarity());
            
            return userName;

        } catch (Exception e) {
            // 人脸识别失败不影响告警记录保存，只记录日志
            log.error("人脸识别异常: 告警类型={}", algorithm.getName(), e);
            return null;
        }
    }

    /**
     * 保存人脸识别结果到告警记录
     *
     * @param alarmRecord 告警记录
     * @param violatorName 违规人员姓名
     */
    private void saveFaceRecognitionResult(AlarmRecord alarmRecord, String violatorName) {
        try {
            // 通过姓名查询用户（优先匹配trueName，其次username）
            QueryWrapper<UmsAdmin> queryWrapper = new QueryWrapper<>();
            queryWrapper.and(wrapper -> wrapper
                .eq(UmsAdmin.TRUE_NAME, violatorName)
                .or()
                .eq(UmsAdmin.USERNAME, violatorName)
            );
            UmsAdmin user = umsAdminMapper.selectOne(queryWrapper);

            if (user != null) {
                alarmRecord.setViolatorUserId(user.getId());
                // 注意：相似度信息在这里暂时无法获取，因为已经在前面的识别过程中处理了
                // 如果需要保存相似度，可以修改 performFaceRecognitionAndGetName 返回更多信息
                log.info("保存人脸识别结果: 告警ID={}, 违规人员={}, 用户ID={}", 
                    alarmRecord.getAlarmUid(), violatorName, user.getId());
            } else {
                log.warn("无法保存人脸识别结果: 未找到姓名为 {} 的用户", violatorName);
            }
        } catch (Exception e) {
            log.error("保存人脸识别结果异常: 姓名={}", violatorName, e);
        }
    }

    @Override
    public AlarmRecordVo uploadAlarmInfoByFile(AlarmRecordUploadFileReqDTO uploadFileReqDTO) {
        try {
            // 1. 将 MultipartFile 转换为 Base64
            MultipartFile imageFile = uploadFileReqDTO.getImageFile();
            
            // 卫语句：检查文件是否为空
            if (imageFile == null || imageFile.isEmpty()) {
                Asserts.fail("图片文件不能为空");
                return null;
            }
            
            // 读取文件字节
            byte[] imageBytes = imageFile.getBytes();
            
            // 转换为 Base64（添加 data URI 前缀）
            String base64Image = "data:image/jpeg;base64," + Base64.encode(imageBytes);
            
            // 2. 构建原有的 DTO
            AlarmRecordUploadReqDTO uploadReqDTO = new AlarmRecordUploadReqDTO();
            uploadReqDTO.setImageBase64(base64Image);
            uploadReqDTO.setDeviceSn(uploadFileReqDTO.getDeviceSn());
            uploadReqDTO.setAlarmType(uploadFileReqDTO.getAlarmType().intValue()); // Long转Integer
            uploadReqDTO.setCameraId(uploadFileReqDTO.getCameraId());
            
            // 转换检测位置列表
            if (uploadFileReqDTO.getDetectionPositions() != null && !uploadFileReqDTO.getDetectionPositions().isEmpty()) {
                List<AlarmRecordUploadReqDTO.DetectionPositionDTO> positions = new ArrayList<>();
                for (AlarmRecordUploadFileReqDTO.DetectionPositionDTO filePos : uploadFileReqDTO.getDetectionPositions()) {
                    AlarmRecordUploadReqDTO.DetectionPositionDTO pos = new AlarmRecordUploadReqDTO.DetectionPositionDTO();
                    pos.setX(filePos.getX());
                    pos.setY(filePos.getY());
                    pos.setWidth(filePos.getWidth());
                    pos.setHeight(filePos.getHeight());
                    pos.setTargetType(filePos.getTargetType());
                    pos.setConfidence(filePos.getConfidence());
                    positions.add(pos);
                }
                uploadReqDTO.setDetectionPositions(positions);
            }
            
            // 3. 复用原有的上传逻辑
            return uploadAlarmInfo(uploadReqDTO);
            
        } catch (IOException e) {
            log.error("读取图片文件失败", e);
            Asserts.fail("读取图片文件失败: " + e.getMessage());
            return null;
        }
    }
}
